#include "Character.h"
#include "Map.h"
#include "ProjectileEnemy.h"
#include "EnemyPool.h"

Character::Character()
	: mProjectileIndex (0)
	, kMaxProjectiles (100)
	, mLastHorizontalMovement(Right)
	, mCurrentHealth (5)
	, mLives (3)
{}

Character::~Character()
{
}

void Character::Load()
{
	mSprite.Add("larry3.png");
	mSprite.Add("larry4.png");
	mLifeSprite.Add("larry1life.png");
	mLifeSprite.Add("larry2lives.png");
	mLifeSprite.Add("larry3lives.png");

	for(int i=0; i<kMaxProjectiles; ++i)
	{
		mProjectiles[i].Load("denture");
	}

	mMeleeAttack.Load("cane");
	mShield.Load();
	mCurrentHealth.Load();
	mCurrentHealth.ScaleSprite(2.0f, 2.0f, true);
	mFont.Load(36, true);

	mMeleeSound.Load("larrymelee.wav");
	mProjectileSound.Load("larryprojectile.wav");
	mShieldSound.Load("larryshield.wav");
	mDeathSound.Load("larrydeath.wav");
	mTacoDamage.Load("tacodamage.wav");
}

void Character::Unload()
{
	mSprite.Unload();
	for(int i=0; i<kMaxProjectiles; ++i)
	{
		mProjectiles[i].Unload();
	}

	mMeleeAttack.Unload();
	mShield.Unload();
	mCurrentHealth.Unload();
	mFont.Unload();

	mMeleeSound.Unload();
	mProjectileSound.Unload();
	mShieldSound.Unload();
	mDeathSound.Unload();
	mTacoDamage.Unload();
}

void Character::Update(float deltaTime, Map& map, EnemyPool& ePool)
{
	mSprite.Update(deltaTime);
	mLifeSprite.Update(deltaTime);

	ShieldUp();
	mShield.Update(deltaTime);
	mMeleeAttack.Update(deltaTime);
	for(int i=0; i<kMaxProjectiles; ++i)
	{
		mProjectiles[i].Update(deltaTime, map);
	}

	if(!mShielded)
	{
		HorizontalMovement(deltaTime, map);
		VerticalMovement(deltaTime, map);
		FireProjectile();
		MeleeAttack();
	}

	DealProjectileDamage(ePool);
	DealMeleeDamage(ePool);

	mCurrentHealth.Update(deltaTime, SVector2(500.0f, 30.0f));
	if(!mCurrentHealth.IsAlive())
	{
		mLives--;
		mCurrentHealth.SetCurrentHealth(5);
		mDeathSound.Play(false);
	}
}

void Character::Render(const SVector2& viewOffset) 
{
	//offset to make up for off position, move from corner to the center
	int width = mSprite.GetWidth();
	int height = mSprite.GetHeight();

	SVector2 offset(width * 0.5f, height * 0.5f);

	//add view offset to move from local to global space (keep character in center of screen space)
	SVector2 renderPosition = mPosition - offset - viewOffset; 

	mSprite.SetPosition(renderPosition);
	if(mLastHorizontalMovement == Left)
		mSprite.SetFlipH(true);
	else if (mLastHorizontalMovement == Right)
		mSprite.SetFlipH(false);
	mSprite.Render();

	for(int i=0; i<kMaxProjectiles; ++i)
		mProjectiles[i].Render(viewOffset);

	mMeleeAttack.Render(viewOffset);
	mShield.Render(viewOffset);
	mCurrentHealth.Render(SVector2(0.0f, 0.0f));

	char buffer[128];
    sprintf_s(buffer, 128, "LIVES:");
    mFont.SetColor(252, 173, 144);
    mFont.Print(buffer, 50, 10);

	if(mLives > 0)
	{
		mLifeSprite.SetPosition(150.0f, 10.0f);
		mLifeSprite.SetCurrentFrame(mLives - 1);
		mLifeSprite.Render();
	}
}

SRect Character::GetBoundingBox() const
{
	//need to create an approximate shape for collision etc.
	SRect boundingBox;
	
	//defining x and y minimums of bounding box
	//using magic numbers containing size of content on sprite
	boundingBox.min.x = mPosition.x - 32.0f;
	boundingBox.max.x = mPosition.x + 32.0f;
	boundingBox.min.y = mPosition.y - 70.0f;
	boundingBox.max.y = mPosition.y + 70.0f;

	//moving in positive x, check right line segment of box etc.

	return boundingBox;
}

void Character::HorizontalMovement(float deltaTime, const Map& map)
{
	const float kSpeed = 250.0f; 
	//instead of moving position directly with keys
	//modify displacement (take collision into account) before applying
	float displacementX = 0.0f;

	////////////////////////HORIZONTAL////////////////////////////////////
	if(Input_IsKeyDown(Keys::D))
	{
		displacementX = kSpeed * deltaTime;
		mLastHorizontalMovement = Right;
		mSprite.Play(2.0f, true);
	}
	else if(Input_IsKeyDown(Keys::A))
	{
		displacementX = -kSpeed * deltaTime;
		mLastHorizontalMovement = Left;
		mSprite.Play(2.0f, true);
	}
	else
		mSprite.Stop();

	//shift bounding box to create projection using displacement in x-axis
	SRect boundingBox = GetBoundingBox() + SVector2(displacementX, 0.0f);
	if(displacementX > 0.0f)
	{
		//CHECK RIGHT line segment of bounding box if moving right (positive displacement)
		SLineSegment segment = boundingBox.GetRightSegment();
		
		//check  line segment against map
		SRect collisionBox;
		if(map.TestCollision(segment, collisionBox))
		{
			//prevent movement if there is collision:
			//(characters position must be at least the bounding box width away)
			displacementX -= boundingBox.max.x - collisionBox.min.x - 1.0f;
			mPosition.x += (int)displacementX;
		}
		else
		{
			mPosition.x += (int)displacementX;
		}
		
	}

	else if (displacementX < 0.0f)
	{
		//TO-DO:
		//CHECK LEFT line segment if moving left (negative displacement)
		SLineSegment segment = boundingBox.GetLeftSegment();

		SRect collisionBox;
		if(map.TestCollision(segment, collisionBox))
		{
			displacementX += collisionBox.max.x - boundingBox.min.x + 1.0f;
			mPosition.x += (int)displacementX;
		}
		else
		{
			mPosition.x += (int)displacementX;
		}
	}
}

void Character::VerticalMovement(float deltaTime, const Map& map)
{
	/////////////////////////////////VERTICAL////////////////////////////////
	const float kSpeed = 250.0f;
	float displacementY = 0.0f;

	if(Input_IsKeyDown(Keys::W))
	{
		displacementY = -kSpeed * deltaTime;
		mSprite.Play(2.0f, true);
	}
	else if(Input_IsKeyDown(Keys::S))
	{
		displacementY = kSpeed * deltaTime;
		mSprite.Play(2.0f, true);
	}

	SRect boundingBoxY = GetBoundingBox() + SVector2(0.0f, displacementY);

	if(displacementY > 0.0f)
	{
		SLineSegment segment = boundingBoxY.GetBottomSegment();
		
		SRect collisionBox;
		if(map.TestCollision(segment, collisionBox))
		{
			displacementY -= boundingBoxY.max.y - collisionBox.min.y - 1.0f;
			mPosition.y += (int)displacementY;
		}
		else
		{
			mPosition.y += (int)displacementY;
		}
		
	}

	else if (displacementY < 0.0f)
	{
		SLineSegment segment = boundingBoxY.GetTopSegment();

		SRect collisionBox;
		if(map.TestCollision(segment, collisionBox))
		{
			displacementY += collisionBox.max.y - boundingBoxY.min.y + 1.0f;
			mPosition.y += (int)displacementY;
		}
		else
		{
			mPosition.y += (int)displacementY;
		}
	}
}

void Character::FireProjectile()
{
	if(Input_IsKeyPressed(Keys::UP))
	{
		if(!mProjectiles[mProjectileIndex].isActive())
		{
			SVector2 pos = mPosition;
			SVector2 vel(0.0f, -500.0f);
			mProjectiles[mProjectileIndex].Fire(pos, vel);
			mProjectileIndex = (mProjectileIndex + 1) % kMaxProjectiles;
		}
		mProjectileSound.Play(false);
	}
	else if(Input_IsKeyPressed(Keys::DOWN))
	{
		if(!mProjectiles[mProjectileIndex].isActive())
		{
			SVector2 pos = mPosition;
			SVector2 vel(0.0f, 500.0f);
			mProjectiles[mProjectileIndex].Fire(pos, vel);
			mProjectileIndex = (mProjectileIndex + 1)  % kMaxProjectiles;
		}
		mProjectileSound.Play(false);
	}
	else if(Input_IsKeyPressed(Keys::LEFT))
	{
		if(!mProjectiles[mProjectileIndex].isActive())
		{
			SVector2 pos = mPosition;
			SVector2 vel(-500.0f, 0.0f);
			mProjectiles[mProjectileIndex].Fire(pos, vel);
			mProjectileIndex = (mProjectileIndex + 1) % kMaxProjectiles;
		}
		mProjectileSound.Play(false);
	}
	else if(Input_IsKeyPressed(Keys::RIGHT))
	{
		if(!mProjectiles[mProjectileIndex].isActive())
		{
			SVector2 pos = mPosition;
			SVector2 vel(500.0f, 0.0f);
			mProjectiles[mProjectileIndex].Fire(pos, vel);
			mProjectileIndex = (mProjectileIndex + 1 ) % kMaxProjectiles;
		}
		mProjectileSound.Play(false);
	}
}

void Character::MeleeAttack()
{
	bool facingLeft = false;

	if(Input_IsKeyPressed(Keys::RCONTROL))
	{
		SVector2 pos = mPosition;

		switch(mLastHorizontalMovement)
		{
			case Left:
				pos.x -= 30.0f;
				facingLeft = true;
				break;
			case Right:
				pos.x += 25.0f;
				break;
		}

		mMeleeSound.Play(false);
		mMeleeAttack.Attack(pos, facingLeft);
	}
}

void Character::ShieldUp()
{
	if(Input_IsKeyDown(Keys::SPACE))
	{
		mShield.ActivateShield(mPosition);
		mShielded = true;
	}
	else
		mShielded = false;
	if(Input_IsKeyPressed(Keys::SPACE))
		mShieldSound.Play(false);
}

void Character::DealProjectileDamage(EnemyPool& ePool)
{
	//loop through all projectiles
	for(int p=0; p<kMaxProjectiles; ++p)
	{
		//if projectile is active
		if(mProjectiles[p].isActive())
		{
			//check against every projectile enemy REFACTOR
			for(int e=0; e<ePool.GetMaxEnemies(); ++e)
			{
				if(ePool.GetProjectileEnemy(e) -> isActive())
				{
					const SCircle eCircle = ePool.GetProjectileEnemy(e) -> GetBoundingCircle();
					SCircle pCircle = mProjectiles[p].GetBoundingCircle();

					float xDiff = eCircle.center.x - pCircle.center.x;
					float yDiff = eCircle.center.y - pCircle.center.y;

					//pythagorean to find connecting vector between projectile and enemy
					float distance = sqrt(xDiff * xDiff + yDiff * yDiff);
					float radii = eCircle.radius + pCircle.radius;

					//if distance between e and p is less than combine radii, they have collided
					if(distance <= radii)
					{
						ePool.GetProjectileEnemy(e) -> mCurrentHealth.TakeDamage(1);
						mProjectiles[p].Kill();
						mTacoDamage.Play(false);
						break; //breaks so we do not continue comparing 
					}
				}
			}
			////check against every melee enemy - REFACTOR
			for(int m=0; m<ePool.GetMaxEnemies(); ++m)
			{
				if(ePool.GetMeleeEnemy(m) -> isActive())
				{
					const SCircle mCircle = ePool.GetMeleeEnemy(m) -> GetBoundingCircle();
					SCircle pCircle = mProjectiles[p].GetBoundingCircle();

					float xDiff = mCircle.center.x - pCircle.center.x;
					float yDiff = mCircle.center.y - pCircle.center.y;

					float distance = sqrt(xDiff * xDiff - yDiff * yDiff);
					float radii = mCircle.radius + pCircle.radius;

					if(distance <= radii)
					{
						ePool.GetMeleeEnemy(m) -> mCurrentHealth.TakeDamage(1);
						mProjectiles[p].Kill();
						break;
					}
				}
			}
		}
	}
}

void Character::DealMeleeDamage(EnemyPool& ePool)
{
	for(int e=0; e<ePool.GetMaxEnemies(); ++e)
	{
		if(ePool.GetProjectileEnemy(e) -> isActive())
		{
			const SCircle eCircle = ePool.GetProjectileEnemy(e) -> GetBoundingCircle();
			SCircle mCircle = mMeleeAttack.GetBoundingCircle();
			float xDiff = eCircle.center.x - mCircle.center.x;
			float yDiff = eCircle.center.y - mCircle.center.y;
			float distance = sqrt(xDiff * xDiff + yDiff * yDiff);
			float radii = eCircle.radius + mCircle.radius;

			if(distance <= radii)
			{
				ePool.GetProjectileEnemy(e) -> mCurrentHealth.TakeDamage(1);
				mTacoDamage.Play(false);
				break;
			}
		}
	}

	for(int e=0; e<ePool.GetMaxEnemies(); ++e)
	{
		if(ePool.GetMeleeEnemy(e) -> isActive())
		{
			const SCircle eCircle = ePool.GetMeleeEnemy(e) -> GetBoundingCircle();
			SCircle mCircle = mMeleeAttack.GetBoundingCircle();
			float xDiff = eCircle.center.x - mCircle.center.x;
			float yDiff = eCircle.center.y - mCircle.center.y;
			float distance = sqrt(xDiff * xDiff + yDiff * yDiff);
			float radii = eCircle.radius + mCircle.radius;

			if(distance <= radii)
			{
				//map.DealDamageToMeleeEnemy(e,1);
				ePool.GetMeleeEnemy(e) -> mCurrentHealth.TakeDamage(1);
				break;
			}
		}
	}

}